package com.wyj;

import com.google.common.collect.Lists;
import com.wyj.pojo.Author;
import com.wyj.pojo.Book;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * java8当中的新特性，主要是我们对集合跟数组的链状流式操作。
 */
@SpringBootTest
public class streamTexts {

    private List<Author> getAuthors = new ArrayList<>();

    //在每次测试之前执行
    @BeforeEach
    void beforeText() {
        Author author1 = new Author(1L, "王", 21, "很牛逼", null);
        Author author2 = new Author(2L, "张", 24, "挺棒的", null);
        Author author3 = new Author(3L, "杨", 43, "还不错", null);
        Author author4 = new Author(4L, "李", 23, "差不多", null);

        List<Book> book1 = new ArrayList<>();
        List<Book> book2 = new ArrayList<>();
        List<Book> book3 = new ArrayList<>();
        List<Book> book4 = new ArrayList<>();

        book1.add(new Book(1L, "成功学1", "人文", 100, "很值得看"));
        book1.add(new Book(2L, "成功学2", "人文", 21, "差不多"));
        book1.add(new Book(3L, "成功学3", "哲学", 120, "还行吧"));

        book2.add(new Book(1L, "差点成功了", "爱情", 100, "很值得看"));
        book2.add(new Book(2L, "差点成功了", "社会", 100, "很值得看"));
        book2.add(new Book(3L, "差点成功了", "人文", 100, "很值得看"));


        book3.add(new Book(1L, "没成功", "人文", 100, "很值得看"));
        book3.add(new Book(2L, "没成功", "人文", 100, "很值得看"));
        book3.add(new Book(3L, "没成功", "人文", 100, "很值得看"));


        book4.add(new Book(1L, "没成功", "人文", 100, "很值得看"));
        book4.add(new Book(2L, "没成功", "人文", 100, "很值得看"));
        book4.add(new Book(3L, "没成功", "人文", 100, "很值得看"));

        author1.setBooks(book1);
        author2.setBooks(book2);
        author3.setBooks(book3);
        author4.setBooks(book4);

        getAuthors.add(author1);
        getAuthors.add(author2);
        getAuthors.add(author3);
        getAuthors.add(author4);
    }

    /**
     * 我们可以调用getAuthors,方法获取到作家的集合。现在需要打印所有年龄小于24的作家的名字，并且要注意去重。
     */
    @Test
    void text01() {
        //匿名内部类式写法
        getAuthors.stream()
                .distinct()
                .filter(new Predicate<Author>() {
                    @Override
                    public boolean test(Author author) {
                        return author.getAge() < 24;
                    }
                })
                .forEach(new Consumer<Author>() {
                    @Override
                    public void accept(Author author) {
                        System.out.println(author.getName());
                    }
                });
        //lambda方式的写法
        getAuthors.stream()
                .distinct()//去重
                .filter((author -> author.getAge() < 24))
                .forEach((author -> System.out.println(author.getName())));

    }

    /**
     * 创建流
     * 单列集合
     * 数组
     * 双列集合
     */
    @Test
    void text02() {
        //单列集合
        ArrayList<Integer> list = new ArrayList<>();
        list.sort(((o1, o2) -> o1.compareTo(o2)));//升序处理
        Stream<Integer> stream = list.stream();

        //数组
        int[] arr = {1, 3, 3, 4, 5};
        IntStream stream1 = Arrays.stream(arr);
        stream1.distinct().forEach(value -> System.out.println(value));

        //双列集合 先转换成单列集合再创建
        Map<String, Integer> map = new HashMap<>();
        map.put("wyj1", 1);
        map.put("wyj1", 1);
        map.put("wyj1", 1);

        //将其转换成单列集合
        Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
        Stream<Map.Entry<String, Integer>> stream2 = entrySet.stream();

        stream2.distinct().forEach(stringIntegerEntry -> {
            System.out.println(stringIntegerEntry.getKey());
            System.out.println(stringIntegerEntry.getValue());
        });

    }

    /**
     * 中间操作之map
     * 可以对流中的元素进行计算或者是转换
     * 示例：打印所有作家的姓名
     */
    @Test
    void text03() {
        //对流当中的元素进行转换 我们通过map将流中的元素由 author 转换成了name
        getAuthors.stream()
                .map(author -> author.getName())
                .forEach(name -> System.out.println(name));

        //对流当中的元素进行转换加计算
        getAuthors.stream()
                .map(author -> author.getAge())
                .map(age -> age + 10)
                .forEach(age -> System.out.println(age));
    }

    /**
     * 找中间操作之sored
     * 将author当中按照年龄进行升序处理
     */
    @Test
    void text04() {
        getAuthors.stream()
                .sorted(((o1, o2) -> o1.getAge().compareTo(o2.getAge())))
                .forEach(author -> System.out.println(author));
    }


    /**
     * limit
     * 可以设置流的最大长度，超出的部分就可以被抛弃
     * 示例:对流中的元素按照年龄进行降序排序，并且要求不能有重复的元素，然后打印其中年龄最大的两个作家的姓名。
     */

    @Test
    void text05() {
        getAuthors.stream()
                .sorted(((o1, o2) -> o2.getAge().compareTo(o1.getAge())))
                .distinct()
                .limit(2)
                .forEach(author -> System.out.println(author));
    }

    /**
     * Skip
     * 跳过流当中的前几个元素，返回剩下的元素
     * 示例:打印除了年龄最大的作家外的其他作家，要求不能有重复元素，并且按照年龄降序排序。
     */
    @Test
    void text06() {
        getAuthors.stream()
                .sorted((o1, o2) -> o2.getAge().compareTo(o1.getAge()))
                .distinct()
                .skip(1)
                .forEach(author -> System.out.println(author));
    }

    /**
     * flatMap
     * map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
     * 示例：打印所有书籍的名字，要求重复的元素进行去重
     * 示例：打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式：哲学，爱情
     */
    @Test
    void text07() {
        //示例1：打印所有书籍的名字，要求重复的元素进行去重
        getAuthors.stream()
                .flatMap(new Function<Author, Stream<Book>>() {//<Author,Stream<Book>> 这里的话我们定义的泛型是Book 那么我们下方遍历出来的就是book集合
                    @Override
                    public Stream<Book> apply(Author author) {
                        return author.getBooks().stream();
                    }
                })
                .distinct()
                .forEach(book -> System.out.println(book));

        // 示例：打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式：哲学，爱情
        getAuthors.stream()
                .flatMap(new Function<Author, Stream<Book>>() {
                    @Override
                    public Stream<Book> apply(Author author) {
                        return author.getBooks().stream();
                    }
                })
                .map((book) -> book.getCategory())//将book对象中的字段进行过滤 获取到我们想要的字段 分类字段
                .distinct()//将分类进行去重
                .filter(new Predicate<String>() {//筛选一下不包含哲学跟爱情
                    @Override
                    public boolean test(String s) {
                        if (s.equals("哲学")) return false;
                        if (s.equals("爱情")) return false;
                        return true;
                    }
                })
                .forEach(cat -> System.out.println(cat));
    }

    /**
     * count 终结操作
     * 获取流当中的元素个数
     * 示例：打印这些作家的所出书籍的数目，注意删除重复元素。
     */
    @Test
    void text08() {
        long count = getAuthors.stream()
                .map(author -> author.getBooks())//对获取到的字段进行过滤 找到books
                .distinct()
                .count();

        System.out.println(count);

    }

    /**
     * max && min 终结操作
     * 用于获取流当中的最值
     * 示例:分别获取这些作家的所出书籍的最高分和最低分并打印。
     */
    @Test
    void text09() {
        //取出作家书籍当中的评分的最大值   author<stream>  --> book<stream> -->取出分数
        Optional<Integer> max = getAuthors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getScore())
                .max(((o1, o2) -> o1.compareTo(o2)));
        System.out.println(max.get());

        //最小值
        Optional<Integer> min = getAuthors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getScore())
                .min(((o1, o2) -> o2.compareTo(01)));

        System.out.println(min.get());


    }

    /**
     * @return void
     * @throws
     * @description collect 终结操作
     * 把当前的流转换成一个集合
     * 示例一：获取一个存放所有玩家姓名的List的集合
     * 示例二：获取一个所有书名的set集合
     * 示例三：获取一个Map集合，map的key为作者名字，value为List<Book>
     * @author wyj
     * @date 2022/12/17 8:12 下午
     * @Param:
     */
    @Test
    void text10() {

        //获取一个存放所有玩家姓名的List的集合 将流转换成一个list集合
        List<String> list = getAuthors.stream()
                .map(author -> author.getName())
                .collect(Collectors.toList());

        for (String s : list) {
            System.out.println(s);
        }

        //获取一个所有书名的set集合 将流转换成set集合
        Set<String> set = getAuthors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getName())
                .collect(Collectors.toSet());
        for (String s : set) {
            System.out.println(s);
        }

        //获取一个map集合，map的key为作者姓名，value为List<Book>
        getAuthors.stream()
                .collect(Collectors.toMap(new Function<Author, String>() {
                    @Override
                    public String apply(Author author) {
                        return author.getName();
                    }
                }, new Function<Author, List<Book>>() {

                    @Override
                    public List<Book> apply(Author author) {
                        return author.getBooks();
                    }
                }, (entry1, entry2) -> entry1));


        Map<String, List<Book>> collect = getAuthors.stream()
                .collect(Collectors.toMap(
                        author -> author.getName(),
                        author -> author.getBooks(),
                        (entry1, entry2) -> entry1 //当键值出现重复的时候 需要添加上这个去重

                ));


        //遍历双列集合 创建流 先将我们的双列集合转换为单列集合
        Set<Map.Entry<String, List<Book>>> entries = collect.entrySet();

        entries.stream()
                .forEach(stringListEntry -> {
                    System.out.println(stringListEntry.getKey());
                    System.out.println(stringListEntry.getValue());
                });

    }

    /**
     * @return
     * @throws
     * @description 查找与匹配 anyMatch
     * 可以用来判断是否有任意符合匹配条件的元素，结果为boolean类型。
     * @author wyj
     * @date 2022/12/18 3:58 下午
     * @Param: null
     */
    @Test
    void text11() {
        boolean b = getAuthors.stream()
                .anyMatch(new Predicate<Author>() {
                    @Override
                    public boolean test(Author author) {
                        return author.getAge() > 10;
                    }
                });

        System.out.println(b);

        //简化写法
        boolean b1 = getAuthors.stream()
                .anyMatch(author -> author.getAge() > 50);
        System.out.println(b1);

    }

    /**
     * @return
     * @throws
     * @description 查找与匹配 AllMatch
     * 可以用来判断是否有符合匹配条件的 所有元素，结果为boolean类型。(必须全部元素均需满足)。
     * 判断所有的作家是否全部为成年人
     * @author wyj
     * @date 2022/12/18 4:07 下午
     * @Param: null
     */
    @Test
    void text12() {

        boolean b = getAuthors.stream()
                .allMatch(new Predicate<Author>() {
                    @Override
                    public boolean test(Author author) {
                        return author.getAge() > 18;
                    }
                });

        System.out.println(b);

        //简化写法
        boolean b1 = getAuthors.stream()
                .allMatch(author -> author.getAge() > 18);

        System.out.println(b1);

    }

    /**
     * @return
     * @throws
     * @description 查找与匹配 noneMatch
     * 查找流当中都不符合条件的元素，如果都符合结果为true,否则结果为false
     * 示例：判断作家是否都没有超过100岁的
     * @author wyj
     * @date 2022/12/18 4:15 下午
     * @Param: null
     */
    @Test
    void text13() {
        boolean b = getAuthors.stream()
                .noneMatch(new Predicate<Author>() {
                    @Override
                    public boolean test(Author author) {
                        return author.getAge() > 100;
                    }
                });

        System.out.println(b);

        boolean b1 = getAuthors.stream()
                .noneMatch(author -> author.getAge() > 100);

        System.out.println(b1);
    }

    /**
     * @return
     * @throws
     * @description 查找与匹配 findAny
     * 获取流当中的任意一个元素，该方法没有办法获取到的一定是流当中的第一个元素
     * 示例：获取任意一个大于18的作家，如果存在就输出他的名字
     * @author wyj
     * @date 2022/12/18 4:25 下午
     * @Param: null
     */
    @Test
    void text14() {
        Optional<Author> any = getAuthors.stream()
                .filter(author -> author.getAge() > 18)
                .findAny();

        any.ifPresent(author -> System.out.println(author.getName()));

    }

    /**
     * @return
     * @throws
     * @description findFirst
     * 获取流当中的第一个元素
     * 示例:获取一个年龄最小的作家，并输出他的姓名。
     * @author wyj
     * @date 2022/12/18 4:37 下午
     * @Param: null
     */

    @Test
    void text15() {
        Optional<Author> first = getAuthors.stream()
                .sorted((o1, o2) -> o1.getAge().compareTo(o2.getAge()))
                .findFirst();

        first.ifPresent(author -> System.out.println(author.getName()));
    }

    /**
     * @return
     * @throws
     * @description 对流当中的数据按照你制定的计算方式计算出一个结果。
     * reduce的作用是把stream中的元素给组合起来，我们可以传入一个初始值，
     * 它会按照我们的计算方式依次拿流中的元素和在初始化值的基础上进行计算，计算结果再和后面的元素计算。
     * <p>
     * 示例一：使用reduce求出所有的作者的年龄和
     * <p>
     * 示例二：使用reduce求出所有作者当中的年龄的最大值
     * <p>
     * 示例三：使用reduce求出所有作者中年龄的最小值
     * @author wyj
     * @date 2022/12/18 5:05 下午
     * @Param: null
     */

    @Test
    void text16() {
        // 示例一：使用reduce求出所有的作者的年龄和
        Integer reduce = getAuthors.stream()
                .map(author -> author.getAge())
                .reduce(0, new BinaryOperator<Integer>() {
                    @Override
                    public Integer apply(Integer result, Integer integer) {
                        return result + integer;
                    }
                });
        System.out.println("使用reduce求出所有的作者的年龄和:" + reduce);

        //简化写法
        Integer reduce1 = getAuthors.stream()
                .map(author -> author.getAge())
                .reduce(0, (result, integer) -> result + integer);

        //示例二：使用reduce求出所有作者当中的年龄的最大值
        Optional<Integer> max = getAuthors.stream()
                .map(author -> author.getAge())
                .max((o1, o2) -> o1.compareTo(o2));
        System.out.println("使用reduce求出所有作者当中的年龄的最大值max终结符:" + max.get());

        Integer reduce2 = getAuthors.stream()
                .map(author -> author.getAge())
                .reduce(Integer.MIN_VALUE, new BinaryOperator<Integer>() {
                    @Override
                    public Integer apply(Integer result, Integer integer2) {
                        return result < integer2 ? integer2 : result;
                    }
                });
        //简化写法
        Integer reduce3 = getAuthors.stream()
                .map(author -> author.getAge())
                .reduce(Integer.MIN_VALUE, (result, integer) -> result < integer ? integer : result);

        System.out.println("使用reduce求出所有作者当中的年龄的最大值:" + reduce3);


        //示例三：使用reduce求出所有作者中年龄的最小值
        Integer reduce4 = getAuthors.stream()
                .map(author -> author.getAge())
                .reduce(Integer.MAX_VALUE, (result, integer) -> result > integer ? integer : result);
        System.out.println("使用reduce求出所有作者中年龄的最小值:"+reduce4);

    }


    /**
     * @description Optional是用来避免空指针异常使代码更加优雅
     * @author wyj
     * @date 2022/12/18 6:36 下午
     * @Param: null
     * @return
     * @throws
     */
    public static Author getAuthor() {
        Author author = new Author();
        author.setId(0L);
        author.setName("王永杰");
        author.setAge(0);
        author.setIntro("");
        author.setBooks(Lists.newArrayList());

        return null;
    }

    @Test
    void text17() {
        Author author = getAuthor();

        //反例  不用optional的话 会出现空指针异常
        System.out.println(author.getName());

        //使用 optional的话 不会出现空指针异常
        Optional<Author> author1 = Optional.ofNullable(author);
        author1.ifPresent(author2 -> System.out.println(author2.getName()));

    }



}


























